package edu.nf.vrs.reservation.service;

import cn.hutool.core.bean.BeanUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.pagehelper.PageInfo;
import edu.nf.reservation.inf.ReservationService;
import edu.nf.reservation.dto.ReservationDTO;
import edu.nf.reservation.dto.ReservationPageDTO;
import edu.nf.reservation.exception.ReservationException;
import edu.nf.vrs.reservation.config.RabbitConfig;
import edu.nf.vrs.reservation.dao.ReservationDao;
import edu.nf.vrs.reservation.entity.Reservation;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.nf.vrs.userservice.dto.UserDTO;
import org.nf.vrs.userservice.inf.UserService;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @author Lv
 * @date 2022/3/30
 */
@Slf4j
@DubboService(interfaceClass = ReservationService.class,version = "1.0.0")
@Transactional(rollbackFor = RuntimeException.class)
public class ReserveServiceImpl implements ReservationService {

    @DubboReference(interfaceClass = UserService.class,version = "1.0.0",check = false)
    private UserService userService;

    @Autowired
    private ReservationDao reservationDao;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private Cache<String,Integer> localCache;

    /**
     * 缓存的key前缀
     */
    private static final String RESERVE_PREFIX = "period:";

    /**
     * 查询所有预约
     * @param reservationDTO
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public ReservationPageDTO listReservation(ReservationDTO reservationDTO, Integer pageNum, Integer pageSize) {
        Reservation reservation = BeanUtil.copyProperties(reservationDTO, Reservation.class);
        //封装查询条件
        List<UserDTO> userDTOList = null;
        List<ReservationDTO> reservationDTOList = new ArrayList<>();

        // 从预约信息拿到用户姓名，如果有就根据姓名查询预约信息。
        if (reservationDTO != null && reservationDTO.getUserName() != null){
            userDTOList = userService.queryUserByName(reservationDTO.getUserName());
        }
        //根据姓名查询，得到查询结果
        if (userDTOList != null && userDTOList.size() >0){
            return getReservationPageDTO(pageNum,pageSize,reservation,userDTOList,reservationDTOList);
        }

        //根据姓名查询，没有得到查询结果
        if (userDTOList != null && userDTOList.size() == 0){
            PageInfo<ReservationDTO> pageInfo = new PageInfo<>(reservationDTOList);
            ReservationPageDTO pageDTO = BeanUtil.copyProperties(pageInfo, ReservationPageDTO.class);
            return pageDTO;
        }

        //根据姓名，没有得到查询结果
        return getReservationPageDTO(pageNum,pageSize,reservation,reservationDTOList);
    }

    /**
     * 没有根据姓名条件查询
     * @param pageNum
     * @param pageSize
     * @param reservation
     * @param reservationDTOList
     * @return
     */
    private ReservationPageDTO getReservationPageDTO(Integer pageNum,Integer pageSize,Reservation reservation,List<ReservationDTO> reservationDTOList){
        List<Reservation> reservationList = reservationDao.listReservation(reservation, pageNum, pageSize);
        for (Reservation r : reservationList){
            UserDTO user = userService.getUserById(r.getUid());
            ReservationDTO dto = initData(r, user);
            reservationDTOList.add(dto);
        }
        PageInfo<ReservationDTO> pageInfo = new PageInfo<>(reservationDTOList);
        ReservationPageDTO pageDTO = BeanUtil.copyProperties(pageInfo, ReservationPageDTO.class);
        log.info("====>"+pageInfo);
        return pageDTO;
    }

    /**
     * 根据姓名查询，得到结果
     * @param pageNum
     * @param pageSize
     * @param userDTOList
     * @param reservationDTOList
     * @return
     */
    private ReservationPageDTO getReservationPageDTO(Integer pageNum,Integer pageSize,Reservation reservation,List<UserDTO> userDTOList,List<ReservationDTO> reservationDTOList){
        // 循环用户列表
        for (UserDTO user : userDTOList){
            List<Reservation>reservationList = reservationDao.listReservation(reservation, pageNum, pageSize);
            //拿到用户数据进行查询预约信息
            for (Reservation r : reservationList){
                ReservationDTO dto = initData(r, user);
                log.info("======>"+ dto);
                reservationDTOList.add(dto);
            }
        }
        //封装分页信息
        PageInfo<ReservationDTO> pageInfo = new PageInfo<>(reservationDTOList);
        //将pageInfo复制到ReservationPageDTO
        ReservationPageDTO pageDTO = BeanUtil.copyProperties(pageInfo, ReservationPageDTO.class);
        return pageDTO;
    }

    //封装预约数据
    private ReservationDTO initData(Reservation r,UserDTO userDTO){
        ReservationDTO reservationDTO = BeanUtil.copyProperties(r, ReservationDTO.class);
        BeanUtil.copyProperties(userDTO,reservationDTO);
        return reservationDTO;
    }

    /**
     * 发送消息到exchange
     * @param reservationDTO
     * @throws Exception
     */
    private void sendMessage(ReservationDTO reservationDTO){
        try {
            //转换成json
            String json = new ObjectMapper().writeValueAsString(reservationDTO);
            //创建消息唯一ID
            CorrelationData correlationData = new CorrelationData();
            //设置ID
            correlationData.setId(UUID.randomUUID().toString());
            //发送消息
            rabbitTemplate.convertAndSend(RabbitConfig.EXCHANGE_NAME,"reserve message",json,correlationData);
            //通过setConfirmCallback设置一个回调来确认消息是否发布成功到EXCHANGE中
            rabbitTemplate.setConfirmCallback((cdata,ack,cause)->{
                //如果发布成功，ack则为true，失败则为false
                if (!ack){
                    throw new ReservationException("预约失败，请稍后重试！");
                }
            });
        } catch (Exception e) {
            log.info(e.getMessage());
            throw new ReservationException("预约失败，请稍后重试！");
        }
    }

    /**
     * 检查本地缓存
     * @param reservationDTO
     */
    private void checkLocalCache(ReservationDTO reservationDTO){
        //先判断本地缓存中是否为null,不为null表示已经预约满
        Integer status = localCache.getIfPresent(RESERVE_PREFIX + reservationDTO.getPid());
        if (status != null){
            //如果为1,表示预约已满
            if (status == 1){
                throw new ReservationException("预约已满，请选择其他时间");
                //如果为2，表示该预约时间已经过期
            }else if (status == 2){
                throw new ReservationException("该预约时间已过期，请选择其他时间");
            }
        }
    }

    /**
     * 检查预约时间是否有效，如果缓存中没有则表示无效
     * @param reservationDTO
     */
    private void checkPeriod(ReservationDTO reservationDTO){
        if (!redisTemplate.hasKey(RESERVE_PREFIX + reservationDTO.getPid())){
            localCache.put(RESERVE_PREFIX + reservationDTO.getPid() ,2);
            throw new ReservationException("该预约时间已失效，请稍选择其他时间");
        }
    }

    /**
     * 扣减缓存，并返回疫苗库存剩余量
     * @param reservationDTO
     */
    private void deductCache(ReservationDTO reservationDTO){
        Long stock = redisTemplate.opsForValue().decrement(RESERVE_PREFIX + reservationDTO.getPid());
        if (stock < 0){
            //标记本地缓存
            localCache.put(RESERVE_PREFIX + reservationDTO.getPid(),1);
            //将redis自增回来
            redisTemplate.opsForValue().increment(RESERVE_PREFIX + reservationDTO.getPid());
            throw new ReservationException("预约已满，请看看其他时间");
        }
    }

    /**
     * 添加预约
     * @param reservationDTO
     */
    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void addReservation(ReservationDTO reservationDTO) throws Exception {
        //检查本地缓存
        checkLocalCache(reservationDTO);
        //检查预约时间是否有效
        checkPeriod(reservationDTO);
        //扣减缓存
        deductCache(reservationDTO);
        //向MQ发送消息，生成一条预约记录
        sendMessage(reservationDTO);
    }

    /**
     * 删除预约记录
     * @param reservationId
     */
    @Override
    public void delReservation(int reservationId) {
        try {
            reservationDao.deleteReservation(reservationId);
        } catch (Exception e) {
            //log.info(e.getMessage());
            throw new ReservationException("取消预约失败，请稍后重试",e);
        }
    }

    /**
     * 根据预约ID查询
     * @param reservationId
     * @return
     */
    @Override
    public ReservationDTO selectById(Integer reservationId) {
        try {
            Reservation reservation = reservationDao.selectById(reservationId);
            ReservationDTO dto = BeanUtil.copyProperties(reservation.getUid(), ReservationDTO.class);
            return dto;
        } catch (Exception e) {
            log.info(e.getMessage());
            throw new ReservationException("查询预约失败，请稍后重试",e);
        }
    }

    /**
     * 根据用户编号查询预约记录
     * @param uid
     * @return
     */
    @Override
    public List<ReservationDTO> listReservationByUserId(Integer uid) {
        List<Reservation> reservationList = reservationDao.listReservationByUserId(uid);
        //转换成json数据copy到集合中
        List<ReservationDTO> dtoList = BeanUtil.copyToList(reservationList, ReservationDTO.class);
        return dtoList;
    }
}
